/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;

namespace OsHelper.UsbNet.LibUsb
{
	public class Api
	{
		//List of LibUsb api that is not implemented.
		
		#region API LIBRARY FUNCTIONS - Polling and timing
		
		/// <summary>
		/// Attempt to acquire the event handling lock.
		/// </summary>
		/// <remarks>
		/// <para>This lock is used to ensure that only one thread is monitoring libusb event sources at any one time.</para>
		/// <para>You only need to use this lock if you are developing an application which calls poll() or select() on libusb's file descriptors directly. If you stick to libusb's event handling loop functions (e.g. <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see>) then you do not need to be concerned with this locking.</para>
		/// <para>While holding this lock, you are trusted to actually be handling events. If you are no longer handling events, you must call <see  cref="UnlockEvents">libusb_unlock_events</see> as soon as possible.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 if the lock was obtained successfully.</item>
		/// <item>1 if the lock was not obtained. (i.e. another thread holds the lock)</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_try_lock_events")]
		public static extern int TryLockEvents ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Acquire the event handling lock, blocking until successful acquisition if it is contended. 
		/// </summary>
		/// <remarks>
		/// <para>This lock is used to ensure that only one thread is monitoring libusb event sources at any one time.</para>
		/// <para>You only need to use this lock if you are developing an application which calls poll() or select() on libusb's file descriptors directly. If you stick to libusb's event handling loop functions (e.g. <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see>) then you do not need to be concerned with this locking.</para>
		/// <para>While holding this lock, you are trusted to actually be handling events. If you are no longer handling events, you must call <see  cref="UnlockEvents">libusb_unlock_events</see> as soon as possible.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_lock_events")]
		public static extern void LockEvents ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Release the lock previously acquired with <see  cref="TryLockEvents">libusb_try_lock_events</see> or <see  cref="LockEvents">libusb_lock_events</see>. 
		/// </summary>
		/// <remarks>
		/// <para>Releasing this lock will wake up any threads blocked on <see  cref="WaitForEvent">libusb_wait_for_event</see>.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_unlock_events")]
		public static extern void UnlockEvents ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Determine if it is still OK for this thread to be doing event handling. 
		/// </summary>
		/// <remarks>
		/// <para>Sometimes, libusb needs to temporarily pause all event handlers, and this is the function you should use before polling file descriptors to see if this is the case.</para>
		/// <para>If this function instructs your thread to give up the events lock, you should just continue the usual logic that is documented in Multi-threaded applications and asynchronous I/O. On the next iteration, your thread will fail to obtain the events lock, and will hence become an event waiter.</para>
		/// <para>This function should be called while the events lock is held: you don't need to worry about the results of this function if your thread is not the current event handler.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>1 if event handling can start or continue.</item>
		/// <item>0 if this thread must give up the events lock.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_event_handling_ok")]
		public static extern int EventHandlingOk ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Determine if an active thread is handling events (i.e. if anyone is holding the event handling lock).
		/// </summary>
		/// <remarks>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>1 if a thread is handling events.</item>
		/// <item>0 if there are no threads currently handling events.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_event_handler_active")]
		public static extern int EventHandlerActive ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Acquire the event waiters lock.
		/// </summary>
		/// <remarks>
		/// <para>This lock is designed to be obtained under the situation where you want to be aware when events are completed, but some other thread is event handling so calling <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see> is not allowed.</para>
		/// <para>You then obtain this lock, re-check that another thread is still handling events, then call <see  cref="WaitForEvent">libusb_wait_for_event</see>.</para>
		/// <para>You only need to use this lock if you are developing an application which calls poll() or select() on libusb's file descriptors directly, and may potentially be handling events from 2 threads simultaenously. If you stick to libusb's event handling loop functions (e.g. <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see>) then you do not need to be concerned with this locking.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_lock_event_waiters")]
		public static extern void LockEventWaiters ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Release the event waiters lock. 
		/// </summary>
		/// <remarks>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_unlock_event_waiters")]
		public static extern void UnlockEventWaiters ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Wait for another thread to signal completion of an event. 
		/// </summary>
		/// <remarks>
		/// <para>
		/// This function will block until any of the following conditions are met:
		/// <list type="numbered">
		/// <item>The timeout expires.</item>
		/// <item>A transfer completes.</item>
		/// <item>A thread releases the event handling lock through <see  cref="UnlockEvents">libusb_unlock_events</see>.</item>
		/// </list>
		/// </para>
		/// <para>Condition 1 is obvious. Condition 2 unblocks your thread after the callback for the transfer has completed. Condition 3 is important because it means that the thread that was previously handling events is no longer doing so, so if any events are to complete, another thread needs to step up and start event handling.</para>
		/// <para>This function releases the event waiters lock before putting your thread to sleep, and reacquires the lock as it is being woken up.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <param name="timeval">Maximum timeout for this blocking function.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 after a transfer completes or another thread stops event handling.</item>
		/// <item>1 if the timeout expired.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_wait_for_event")]
		public static extern int WaitForEvent ([In] SessionHandle sessionHandle, ref UnixNativeTimeval timeval);

		/// <summary>
		/// Handle any pending events. 
		/// </summary>
		/// <remarks>
		/// <para>libusb determines "pending events" by checking if any timeouts have expired and by checking the set of file descriptors for activity.</para>
		/// <para>If a non-zero timeval is passed and no events are currently pending, this function will block waiting for events to handle up until the specified timeout. If an event arrives or a signal is raised, this function will return early.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <param name="tv">The maximum time to block waiting for events, or zero for non-blocking mode</param>
		/// <returns>0 on success, or a <see cref="Error"/> code on other failure.</returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_handle_events_timeout")]
		public static extern int HandleEventsTimeout ([In] SessionHandle sessionHandle, ref UnixNativeTimeval tv);

		/// <summary>
		/// Handle any pending events in blocking mode with a sensible timeout.
		/// </summary>
		/// <remarks>
		/// <para>This timeout is currently hardcoded at 2 seconds but we may change this if we decide other values are more sensible. For finer control over whether this function is blocking or non-blocking, or the maximum timeout, use <see  cref="HandleEventsTimeout">libusb_handle_events_timeout</see> instead.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>0 on success, or a <see cref="Error"/> code on other failure.</returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_handle_events")]
		public static extern int HandleEvents ([In] SessionHandle sessionHandle);
		
		/// <summary>
		/// Handle any pending events by polling file descriptors, without checking if any other threads are already doing so. 
		/// </summary>
		/// <remarks>
		/// <para>Must be called with the event lock held, see <see  cref="LockEvents">libusb_lock_events</see>.</para>
		/// <para>This function is designed to be called under the situation where you have taken the event lock and are calling poll()/select() directly on libusb's file descriptors (as opposed to using <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see> or similar). You detect events on libusb's descriptors, so you then call this function with a zero timeout value (while still holding the event lock).</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <param name="tv">The maximum time to block waiting for events, or zero for non-blocking mode</param>
		/// <returns>0 on success, or a <see cref="Error"/> code on other failure.</returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_handle_events_locked")]
		public static extern int HandleEventsLocked ([In] SessionHandle sessionHandle, ref UnixNativeTimeval tv);
		
		/// <summary>
		/// Determines whether your application must apply special timing considerations when monitoring libusb's file descriptors. 
		/// </summary>
		/// <remarks>
		/// <para>This function is only useful for applications which retrieve and poll libusb's file descriptors in their own main loop (The more advanced option).</para>
		/// <para>Ordinarily, libusb's event handler needs to be called into at specific moments in time (in addition to times when there is activity on the file descriptor set). The usual approach is to use <see  cref="GetNextTimeout">libusb_get_next_timeout</see> to learn about when the next timeout occurs, and to adjust your poll()/select() timeout accordingly so that you can make a call into the library at that time.</para>
		/// <para>Some platforms supported by libusb do not come with this baggage - any events relevant to timing will be represented by activity on the file descriptor set, and <see  cref="GetNextTimeout">libusb_get_next_timeout</see> will always return 0. This function allows you to detect whether you are running on such a platform.</para>
		/// <para>Since v1.0.5.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>0 if you must call into libusb at times determined by <see  cref="GetNextTimeout">libusb_get_next_timeout</see>, or 1 if all timeout events are handled internally or through regular activity on the file descriptors.</returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_pollfds_handle_timeouts")]
		public static extern int PollfdsHandleTimeouts ([In] SessionHandle sessionHandle);
		
		/// <summary>
		/// Determine the next internal timeout that libusb needs to handle. 
		/// </summary>
		/// <remarks>
		/// <para>You only need to use this function if you are calling poll() or select() or similar on libusb's file descriptors yourself - you do not need to use it if you are calling <see  cref="HandleEvents(SessionHandle)">libusb_handle_events</see> or a variant directly.</para>
		/// <para>You should call this function in your main loop in order to determine how long to wait for select() or poll() to return results. libusb needs to be called into at this timeout, so you should use it as an upper bound on your select() or poll() call.</para>
		/// <para>When the timeout has expired, call into <see  cref="HandleEventsTimeout">libusb_handle_events_timeout</see> (perhaps in non-blocking mode) so that libusb can handle the timeout.</para>
		/// <para>This function may return 1 (success) and an all-zero timeval. If this is the case, it indicates that libusb has a timeout that has already expired so you should call <see  cref="HandleEventsTimeout">libusb_handle_events_timeout</see> or similar immediately. A return code of 0 indicates that there are no pending timeouts.</para>
		/// <para>On some platforms, this function will always returns 0 (no pending timeouts). See Notes on time-based events.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <param name="tv">The maximum time to block waiting for events, or zero for non-blocking mode</param>
		/// <returns>0 if there are no pending timeouts, 1 if a timeout was returned, or <see cref="Error.ErrorOther"/> on failure.</returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_next_timeout")]
		public static extern int GetNextTimeout ([In] SessionHandle sessionHandle, ref UnixNativeTimeval tv);
		
		/// <summary>
		/// Register notification functions for file descriptor additions/removals. 
		/// </summary>
		/// <remarks>
		/// <para>To remove notifiers, pass NULL values for the function pointers.</para>
		/// <para>Note that file descriptors may have been added even before you register these notifiers (e.g. when a new <see cref="SessionHandle"/> is created).</para>
		/// <para>Additionally, note that the removal notifier may be called during <see cref="Exit"/> (e.g. when it is closing file descriptors that were opened and added to the poll set when a new <see cref="SessionHandle"/> was created). If you don't want this, remove the notifiers immediately before calling <see cref="SafeHandle.Close">SessionHandle.Close()</see>.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <param name="addedDelegate">Function delegate for addition notifications.</param>
		/// <param name="removedDelegate">Function delegate for removal notifications.</param>
		/// <param name="pUserData">User data to be passed back to callbacks (useful for passing sessionHandle information).</param>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_set_pollfd_notifiers")]
		public static extern void SetPollfdNotifiers ([In] SessionHandle sessionHandle, PollfdAddedDelegate addedDelegate, PollfdRemovedDelegate removedDelegate, IntPtr pUserData);
		
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_pollfds")]
		private static extern IntPtr GetPollfdsInternal ([In] SessionHandle sessionHandle);

		/// <summary>
		/// Retrieve a list of file descriptors that should be polled by your main loop as libusb event sources. 
		/// </summary>
		/// <remarks>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="poll"/></note>
		/// </remarks>
		/// <param name="sessionHandle">A valid <see cref="SessionHandle"/>.</param>
		/// <returns>A list of PollfdItem structures, or null on error.</returns>
		public static List<PollfdItem> GetPollfds (SessionHandle sessionHandle)
		{
			List<PollfdItem> rtnList = new List<PollfdItem> ();
			IntPtr pList = GetPollfdsInternal (sessionHandle);
			if (pList == IntPtr.Zero)
				return null;
			
			IntPtr pNext = pList;
			IntPtr pPollfd;
			while ((((pNext != IntPtr.Zero))) && (pPollfd = Marshal.ReadIntPtr (pNext)) != IntPtr.Zero) {
				PollfdItem pollfdItem = new PollfdItem (pPollfd);
				rtnList.Add (pollfdItem);
				pNext = new IntPtr (pNext.ToInt64 () + IntPtr.Size);
			}
			Marshal.FreeHGlobal (pList);
			
			return rtnList;
		}

		#endregion
	}
}
